/* osgEarth
* Copyright 2025 Pelican Mapping
* MIT License
*/
#include <osgEarth/Expression>
#include <osgEarth/StringUtils>

using namespace osgEarth;

#define LC "[Expression] "

NumericExpression::NumericExpression(const std::string& expr) :
    _src(expr)
{
    init();
}

NumericExpression::NumericExpression(double staticValue) :
    _value(staticValue),
    _dirty(false)
{
    _src = std::to_string(staticValue);
    init();
}

NumericExpression::NumericExpression(const Config& conf)
{
    mergeConfig(conf);
    init();
}

void
NumericExpression::setLiteral(double staticValue)
{
    _value = staticValue;
    _dirty = false;
    _src = Stringify() << staticValue;
    init();
}

void
NumericExpression::mergeConfig(const Config& conf)
{
    _src = conf.value();
    init();
    _dirty = true;
}

Config
NumericExpression::getConfig() const
{
    return Config("numeric_expression", _src);
}

#define IS_OPERATOR(a) ( a .first == ADD || a .first == SUB || a .first == MULT || a .first == DIV || a .first == MOD )

void
NumericExpression::init()
{
    _vars.clear();
    _rpn.clear();

    StringTokenizer tokenizeOperands;
    tokenizeOperands
        .delim(",", true)
        .delim("(", true)
        .delim(")", true)
        .delim("%", true)
        .delim("*", true)
        .delim("/", true)
        .delim("+", true)
        .delim("-", true)
        .standardQuotes()
        .keepEmpties(false);

    auto variablesTokens = StringTokenizer()
        .delim("[", true)
        .delim("]", true)
        .standardQuotes()
        .keepEmpties(false)
        .tokenize(_src);

    StringVector t;
    bool invar = false;
    for (unsigned i = 0; i < variablesTokens.size(); ++i)
    {
        if (variablesTokens[i] == "[" && !invar)
        {
            // Start variable, add "[" token
            invar = true;
            t.push_back(variablesTokens[i]);
        }
        else if (variablesTokens[i] == "]" && invar)
        {
            // End variable, add "]" token
            invar = false;
            t.push_back(variablesTokens[i]);
        }
        else if (invar)
        {
            // Variable, add variable token
            t.push_back(variablesTokens[i]);
        }
        else
        {
            // Operands, tokenize it and add tokens
            auto operandTokens = tokenizeOperands(variablesTokens[i]);
            t.insert(t.end(), operandTokens.begin(), operandTokens.end());
        }
    }

    // identify tokens:
    AtomVector infix;
    invar = false;
    for (unsigned i = 0; i < t.size(); ++i) {
        if (t[i] == "[" && !invar) {
            invar = true;
        }
        else if (t[i] == "]" && invar) {
            invar = false;
            infix.push_back(Atom(VARIABLE, 0.0));
            _vars.push_back(Variable(t[i - 1], 0));
        }
        else if (t[i] == "(") infix.push_back(Atom(LPAREN, 0.0));
        else if (t[i] == ")") infix.push_back(Atom(RPAREN, 0.0));
        else if (t[i] == ",") infix.push_back(Atom(COMMA, 0.0));
        else if (t[i] == "%") infix.push_back(Atom(MOD, 0.0));
        else if (t[i] == "*") infix.push_back(Atom(MULT, 0.0));
        else if (t[i] == "/") infix.push_back(Atom(DIV, 0.0));
        else if (t[i] == "+") infix.push_back(Atom(ADD, 0.0));
        else if (t[i] == "-") infix.push_back(Atom(SUB, 0.0));
        else if (t[i] == "min") infix.push_back(Atom(MIN, 0.0));
        else if (t[i] == "max") infix.push_back(Atom(MAX, 0.0));
        else if ((t[i][0] >= '0' && t[i][0] <= '9') || t[i][0] == '.')
            infix.push_back(Atom(OPERAND, as<double>(t[i], 0.0)));
        else if (t[i] != "," && (i == 0 || t[i - 1] != "["))
        {
            std::string var = t[i];
            // If this is a call to a script function, store the entire function
            // call in the variable string
            if (i < t.size() - 1 && t[i + 1] == "(")
            {
                int parenCount = 0;
                do
                {
                    ++i;
                    var += t[i];

                    if (t[i] == "(")
                        parenCount++;
                    else if (t[i] == ")")
                        parenCount--;

                } while (i < t.size() - 1 && parenCount > 0);
            }

            infix.push_back(Atom(VARIABLE, 0.0));
            _vars.push_back(Variable(var, 0));
        }

        // note: do nothing for a comma
    }

    // convert to RPN:
    // http://en.wikipedia.org/wiki/Shunting-yard_algorithm
    AtomStack s;
    unsigned var_i = 0;

    for (unsigned i = 0; i < infix.size(); ++i)
    {
        Atom& a = infix[i];

        if (a.first == LPAREN)
        {
            s.push(a);
        }
        else if (a.first == RPAREN)
        {
            while (s.size() > 0)
            {
                Atom top = s.top();
                s.pop();
                if (top.first == LPAREN)
                    break;
                else
                    _rpn.push_back(top);
            }
        }
        else if (a.first == COMMA)
        {
            while (s.size() > 0 && s.top().first != LPAREN)
            {
                _rpn.push_back(s.top());
                s.pop();
            }
        }
        else if (IS_OPERATOR(a))
        {
            if (s.empty() || a.first > s.top().first)
            {
                s.push(a);
            }
            else
            {
                while (s.size() > 0 && a.first < s.top().first && IS_OPERATOR(s.top()))
                {
                    _rpn.push_back(s.top());
                    s.pop();
                }
                s.push(a);
            }
        }
        else if (a.first == MIN || a.first == MAX)
        {
            s.push(a);
        }
        else if (a.first == OPERAND)
        {
            _rpn.push_back(a);
        }
        else if (a.first == VARIABLE)
        {
            _rpn.push_back(a);
            _vars[var_i++].second = _rpn.size() - 1; // store the index
        }
    }

    while (s.size() > 0)
    {
        _rpn.push_back(s.top());
        s.pop();
    }
}

void
NumericExpression::set(const Variable& var, double value)
{
    Atom& a = _rpn[var.second];
    if (a.second != value)
    {
        a.second = value;
        _dirty = true;
    }
}

double
NumericExpression::eval() const
{
    if (_dirty)
    {
        std::stack<double> s;

        for (unsigned i = 0; i < _rpn.size(); ++i)
        {
            const Atom& a = _rpn[i];

            if (a.first == ADD)
            {
                if (s.size() >= 2)
                {
                    double op2 = s.top(); s.pop();
                    double op1 = s.top(); s.pop();
                    s.push(op1 + op2);
                }
            }
            else if (a.first == SUB)
            {
                if (s.size() >= 2)
                {
                    double op2 = s.top(); s.pop();
                    double op1 = s.top(); s.pop();
                    s.push(op1 - op2);
                }
            }
            else if (a.first == MULT)
            {
                if (s.size() >= 2)
                {
                    double op2 = s.top(); s.pop();
                    double op1 = s.top(); s.pop();
                    s.push(op1 * op2);
                }
            }
            else if (a.first == DIV)
            {
                if (s.size() >= 2)
                {
                    double op2 = s.top(); s.pop();
                    double op1 = s.top(); s.pop();
                    s.push(op1 / op2);
                }
            }
            else if (a.first == MOD)
            {
                if (s.size() >= 2)
                {
                    double op2 = s.top(); s.pop();
                    double op1 = s.top(); s.pop();
                    s.push(fmod(op1, op2));
                }
            }
            else if (a.first == MIN)
            {
                if (s.size() >= 2)
                {
                    double op2 = s.top(); s.pop();
                    double op1 = s.top(); s.pop();
                    s.push(osg::minimum(op1, op2));
                }
            }
            else if (a.first == MAX)
            {
                if (s.size() >= 2)
                {
                    double op2 = s.top(); s.pop();
                    double op1 = s.top(); s.pop();
                    s.push(osg::maximum(op1, op2));
                }
            }
            else // OPERAND or VARIABLE
            {
                s.push(a.second);
            }
        }

        const_cast<NumericExpression*>(this)->_value = s.size() > 0 ? s.top() : 0.0;
        const_cast<NumericExpression*>(this)->_dirty = false;
    }

    return !osg::isNaN(_value) ? _value : 0.0;
}

//------------------------------------------------------------------------


StringExpression::StringExpression(const std::string& expr) :
    _src(expr)
{
    init();
}

StringExpression::StringExpression(const std::string& expr, const URIContext& uriContext) :
    _src(expr),
    _uriContext(uriContext)
{
    init();
}

void
StringExpression::setInfix(const std::string& expr)
{
    _src = expr;
    _dirty = true;
    init();
}

void
StringExpression::setLiteral(const std::string& expr)
{
    _src = "\"" + expr + "\"";
    _value = expr;
    _dirty = false;
}

StringExpression::StringExpression(const Config& conf)
{
    mergeConfig(conf);
    init();
}

void
StringExpression::mergeConfig(const Config& conf)
{
    _src = conf.value();
    _uriContext = conf.referrer();
    _dirty = true;
}

Config
StringExpression::getConfig() const
{
    Config conf("string_expression", _src);
    conf.setReferrer(uriContext().referrer());
    return conf;
}

void
StringExpression::init()
{
    bool inQuotes = false;
    int inVar = 0;
    int startPos = 0;
    for (int i = 0; i < (int)_src.length(); i++)
    {
        if (_src[i] == '"')
        {
            if (inQuotes)
            {
                int length = i - startPos;
                if (length > 0)
                    _infix.push_back(Atom(OPERAND, _src.substr(startPos, length)));

                inQuotes = false;
            }
            else if (!inVar)
            {
                inQuotes = true;
                startPos = i + 1;
            }
        }
        else if (_src[i] == '+' || _src[i] == ' ')
        {
            if (inVar == 1)
            {
                int length = i - startPos;

                //Check for feature attribute access
                if (length > 2 && _src[startPos] == '[' && _src[i - 1] == ']')
                {
                    startPos++;
                    length -= 2;
                }

                if (length > 0)
                {
                    std::string val = _src.substr(startPos, length);
                    _vars.push_back(Variable(val, _infix.size()));
                    _infix.push_back(Atom(VARIABLE, val));
                }

                inVar = 0;
            }
        }
        else if ((_src[i] == '(' || _src[i] == '[' || _src[i] == '{') && inVar)
        {
            inVar++;
        }
        else if ((_src[i] == ')' || _src[i] == ']' || _src[i] == '}') && inVar > 1)
        {
            inVar--;
        }
        else
        {
            if (!inQuotes && !inVar)
            {
                inVar = 1;
                startPos = i;
            }
        }
    }

    if (inVar == 1)
    {
        int length = _src.length() - startPos;

        //Check for feature attribute access
        if (length > 2 &&
            ((_src[startPos] == '[' && _src[_src.length() - 1] == ']') ||
                (_src[startPos] == '(' && _src[_src.length() - 1] == ')') ||
                (_src[startPos] == '{' && _src[_src.length() - 1] == '}'))
            )
        {
            startPos++;
            length -= 2;
        }

        if (length > 0)
        {
            std::string val = _src.substr(startPos, length);
            _vars.push_back(Variable(val, _infix.size()));
            _infix.push_back(Atom(VARIABLE, val));
        }
    }
}

void
StringExpression::set(const Variable& var, const std::string& value)
{
    Atom& a = _infix[var.second];
    if (a.second != value)
    {
        a.second = value;
        _dirty = true;
    }
}

void
StringExpression::set(const std::string& varName, const std::string& value)
{
    for (Variables::const_iterator v = _vars.begin(); v != _vars.end(); ++v)
    {
        if (v->first == varName)
        {
            set(*v, value);
        }
    }
}

const std::string&
StringExpression::eval() const
{
    if (_dirty)
    {
        std::stringstream buf;
        for (AtomVector::const_iterator i = _infix.begin(); i != _infix.end(); ++i)
            buf << i->second;

        const_cast<StringExpression*>(this)->_value = buf.str();
        const_cast<StringExpression*>(this)->_dirty = false;
    }

    return _value;
}

URI
StringExpression::evalURI() const
{
    return URI(eval(), _uriContext);
}
